[上級編]LLMへ至る道~BERTはよく聞くけど実は~[19日目]

[上級編]LLMへ至る道~BERTはよく聞くけど実は~[19日目]

Clock Icon2023.12.19

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

みなさんこんにちは!クルトンです。

前日のブログでは、Transformerモデルでの最後の紹介としてDecoderについて説明しました。

本日のブログでは、BERTについてご紹介いたします。もしかすると自然言語処理の機械学習モデルを試しに動かしてみた事がある方にはお馴染みのモデルかもしれませんね。まずはBERTというモデルがどう使われているかをご説明していきます。

LLM(大規模言語モデル)について

よくある機械学習モデルですと、大量のデータを使って学習を行ない、高精度な推論をできるようにします。しかし大量のデータを用意する事や大量のデータを学習させるためのコンピュータリソースを用意するのは大変難しいです。

LLMの凄いところは、 事前に誰かが学習したモデル(事前学習済みモデル、pre-training model) に対して、少量のデータを使って学習させる(fine tuning)と高精度の推論ができるようになる点です。

road-to-llm-advent-calendar-2023-19-01

road-to-llm-advent-calendar-2023-19-02

BERTとは?どういうモデルなの?

自然言語処理において、さまざまなタスクで幅広く利用可能なモデルです。論文を読んでみたい方は以下からアクセスできます。

以下にどういうタスクで使えるのか列挙してみます。

  • 文章分類
  • 質問応答
  • 機械翻訳
  • 固有表現抽出
  • 文章の類似度判定
  • 文章の穴埋め(予測)

上記のようにさまざまなタスクで使用できるモデルがBERTです。色々な事ができるモデルですね。

ちなみにBERTはGoogle検索においても使われるような高性能な機械学習モデルです。

BERTとTransformerの関係性

BERTという略語をずっと使ってきましたが、BERTを略さずに書くとBidirectional Encoder Representations from Transformersという名前です。そう、実はBERTはTransformerモデルを使っているモデルになります。

Birirectionalなので、双方向という日本語訳ができます。この語の意味するところは、Transformerを2つ使ったモデルという事です。さらに言えば、TransformerのEncoder部分のみを2つ使っているモデルになります。

road-to-llm-advent-calendar-2023-19-03

2つのTransformer Encoderの使い方ですが、文を前から学習していくものと文を後ろから学習していくものを使って、ある単語と前後にある単語との関係を学習しています。

BERTの中身をチェック!

BERTの中身について以下に図示します。

road-to-llm-advent-calendar-2023-19-04

言葉予測(Masked Language Model, 以降はMLMと呼称)次文予測(Next Sentence Prediction, 以降はNSPと呼称) を同時に学習しているモデルになります。

それぞれでやっている事は以下のようなものです。

  • MLM
    • トークン単位(単語単位)での予測
    • 予測を行なう対象を入力されたトークンからランダムに15%選ぶ
      • 選択トークンのうち、80%を[MASK]トークンに置き換え
      • 残りの10%は語彙に含まれるランダムなトークンに置き換え(図ですと「ゴロゴロ」というトークンに置き換えています)
      • 残りの10%は元のトークンそのままで予測をする
  • NSP
    • 文単位での予測
    • 文と文の間には[SEP]トークンがあり、前後の文に繋がりがあるか予測する
      • 質問と回答のようなお仕事をお任せするのに必要な学習
    • 学習時には50%が前後の文が繋がるものを入力し、残り50%はランダムな文が2つ目の文に選ばれる

図中では[CLS]と[SEP]というものが使われていますが、これらは2つの文を入力した時に追加される特殊トークンと呼ばれるものです。

BERTとTransformer Encoderとの違い

以下の2つが異なります。

  • 活性化関数の違い
  • 入力データ埋め込み時の違い

活性化関数

Transforerモデルでは活性化関数にReLUを使っていましたね。 BERTでは、GELUを使っています。両者の違いは3日目のブログをご覧ください。

上記内容はMSMの時の話で、NSPの時は違う活性化関数を使っています。以下のものです。

tanh ( x ) = exp ( x ) exp ( x ) exp ( x ) + exp ( x )

ざっくりとした説明になってしまいますが、シグモイド関数では0〜1の範囲で収まるグラフでしたがtanhでは -1〜1の範囲で収まるグラフになります。

単語埋め込み時

単語を埋め込むとき、Transformerモデルでは、単語を埋め込んだ後に位置符号をしていました。それにプラスして、セグメント埋め込みというものをします。

セグメント埋め込みをする理由は、BERTでは常に2つの文を入れて学習をするため、2つの文のうちどちらが前なのか後ろなのかを判別するためです。 (BERTから派生したモデルでは使っていないものもあります。)

BERTを簡単に動かしてみる

Google Colab(ランタイムはCPU)です。

やってみるタスクは以下です。

  • 文書分類
  • 文の穴埋め
  • 質問応答
  • 文章の類似度判定(BERTScore)

なお、今回はファインチューニングせずにいきなりモデルを動かしてしまいます。出力に以下のように「fine-tuningしてね!」のようなWARNING文が表示されますが、今回は気にしなくてOKです。 出力に含まれている場合は、以下では省いて表示しています。

Some weights of BertForQuestionAnswering were not initialized from the model checkpoint at bert-base-uncased and are newly initialized: ['qa_outputs.bias', 'qa_outputs.weight']
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.

まずは共通で必要なモジュールをインストールします。

!pip install transformers

文書分類

from transformers import pipeline, BertJapaneseTokenizer, BertForSequenceClassification, AutoModelForSequenceClassification
import torch

nlp = pipeline("sentiment-analysis")

text = ["I love you.", "I hate you.", "As I was walking along my usual path, I came across a cute cat, and my cheeks instantly melted.", "That movie wasn't fun.", "I feel relax by watching cat videos on YouTube."]
print(nlp(text))

出力は以下です。

[{'label': 'POSITIVE', 'score': 0.9998705387115479}, {'label': 'NEGATIVE', 'score': 0.9992952346801758}, {'label': 'POSITIVE', 'score': 0.9994725584983826}, {'label': 'NEGATIVE', 'score': 0.999755322933197}, {'label': 'POSITIVE', 'score': 0.9822090864181519}]

scoreと書かれているのは確率です。

肯定的な "I love you"のような文だとポジティブな文であると判定し、 "I hate you"のような文だとネガティブな文であると判定していますね!

文の穴埋め

文の穴埋めに必要なモジュールをインストールします。

!pip install fugashi ipadic
from transformers import BertJapaneseTokenizer, BertForMaskedLM

model_name = "cl-tohoku/bert-base-japanese-whole-word-masking"
tokenizer = BertJapaneseTokenizer.from_pretrained(model_name)
model = BertForMaskedLM.from_pretrained(model_name)

input_text = '今日も猫は[MASK]。'
print(tokenizer.tokenize(input_text)) # ['今日', 'も', '猫', 'は', '[MASK]', '。']
input_ids = tokenizer.encode(input_text, return_tensors="pt")

with torch.no_grad():
  outputs = model(input_ids)
print(input_ids[0]) # tensor([   2, 3246,   28, 6040,    9,    4,    8,    3]) ←MASKのインデックス5に4の値が入っているのが分かる(先頭は[CLS]と最後は[SEP]が入っている)
predicted_token_index = torch.argmax(outputs.logits[0,5]).item()

predicted_token = tokenizer.decode([predicted_token_index])

print(input_text.replace('[MASK]', predicted_token))

出力は以下です。

['今日', 'も', '猫', 'は', '[MASK]', '。']
tensor([   2, 3246,   28, 6040,    9,    4,    8,    3])
今日も猫は健在。

出力された文は「今日も猫は健在。」でした。

質問応答

from transformers import BertTokenizer, BertForQuestionAnswering

tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertForQuestionAnswering.from_pretrained('bert-base-uncased')

context = "BERT is a powerful language model."
question = "What is BERT?"
inputs = tokenizer(context, question, return_tensors="pt")
with torch.no_grad():
  outputs = model(**inputs)

answer_start = torch.argmax(outputs.start_logits)
answer_end = torch.argmax(outputs.end_logits)
answer = tokenizer.decode(inputs["input_ids"][0][answer_start:answer_end+1], skip_special_tokens=True)

print(f"Answer: {answer}")

出力は以下です。

Answer: powerful language model.

「BERTってなに?」という質問に対して、「強力な言語モデルです!」と返答しています。

文章の類似度判定(BERTScore)

BERTScoreでは、2つの文をBERTに与えた時に得られる分散表現をコサイン類似度を使って単語ごとに類似しているか判定する手法です。

また、適合率や再現率なども計算してくれるものです。お忘れの方は5日に公開したブログをご参照ください。

早速動かしてみます。

2つの文は以下のように設定しました。猫が寝ている文についてです。

  • A cat is sleeping in my chair.
  • A cat is sleeping on my bed.
from bert_score import score
from bert_score import plot_example

# テキスト
text = "A cat is sleeping in my chair."


# 参照テキスト(要約など)
reference_text = "A cat is sleeping on my bed."

# BERTScoreの計算
precision, recall, f1 = score(, [reference_text], lang="en")

# 結果の表示
print(f"Precision: {precision.item():.4f}")
print(f"Recall: {recall.item():.4f}")
print(f"F1 Score: {f1.item():.4f}")

# plot
plot_example(text, reference_text, lang="en")

出力は以下のようになりました。

Precision: 0.9846
Recall: 0.9846
F1 Score: 0.9846

road-to-llm-advent-calendar-2023-19-05

寝ている場所が違うだけなので、bedとchairが似ているという判定であったりとなかなか面白い結果になったのではないでしょうか。

終わりに

本日はBERTについてご紹介してきました。BERTというモデルですが、中身としてはTransformer Encoderを使っているモデルです。 Transformerを理解していれば、スムーズに理解できそうでしたね!

明日からは、GPTシリーズのモデルを順番にご紹介していきます!

本日はここまで。よければ、明日もご覧ください!

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.